package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/html"
)

// RetryProcessor 失败文件重试处理器
type RetryProcessor struct {
	config          QwenConfig
	htmlBasePath    string
	outputPath      string
	categoryMapping map[string]string
	client          *http.Client
	systemPrompt    string
	concurrency     int
}

// NewRetryProcessor 创建新的重试处理器
func NewRetryProcessor(htmlBasePath, outputPath string, config QwenConfig, concurrency int) *RetryProcessor {
	// 确保输出目录存在
	os.MkdirAll(outputPath, 0755)

	categoryMapping := map[string]string{
		"school-news":  "学校新闻",
		"xueyuan-news": "学院新闻",
		"xsdt":         "学生动态",
		"fdyw":         "佛大要闻",
		"department":   "部门信息",
		"bls":          "管理学院",
		"fzxy":         "法学院",
		"jmxy":         "经济管理学院",
		"lnxy":         "岭南学院",
	}

	systemPrompt := `你是佛山大学智能问答系统的数据处理专家。请分析HTML内容，提取结构化信息并严格按JSON格式输出。

输出要求：
1. 只输出JSON，不要任何解释文字
2. 生成3-5个高质量问答对
3. 问答对要覆盖不同类型（事实、流程、政策、时间等）

JSON格式：
{
  "title": "页面主标题",
  "category": "分类（学校新闻/学院动态/教务信息/学生服务等）",
  "publish_date": "发布日期YYYY-MM-DD",
  "keywords": ["关键词1", "关键词2"],
  "summary": "页面内容摘要",
  "content": "完整内容文本",
  "qa_pairs": [
    {
      "question": "问题",
      "answer": "详细答案",
      "type": "问题类型（事实型/流程型/政策型/时间型等）",
      "difficulty": "简单/中等/困难"
    }
  ]
}`

	return &RetryProcessor{
		config:          config,
		htmlBasePath:    htmlBasePath,
		outputPath:      outputPath,
		categoryMapping: categoryMapping,
		client:          &http.Client{Timeout: 180 * time.Second}, // 设置为3分钟超时
		systemPrompt:    systemPrompt,
		concurrency:     concurrency,
	}
}

// FailedFileInfo 失败文件信息
type FailedFileInfo struct {
	FileName     string
	FailedPath   string
	OriginalPath string
}

// getFailedFiles 获取所有失败文件的信息
func (rp *RetryProcessor) getFailedFiles() ([]FailedFileInfo, error) {
	failedDir := filepath.Join(rp.outputPath, "failed_files")
	var failedFiles []FailedFileInfo

	err := filepath.Walk(failedDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 只处理FAILED_开头的txt文件
		if strings.HasPrefix(info.Name(), "FAILED_") && strings.HasSuffix(info.Name(), ".txt") {
			// 读取文件最后一行获取原始文件路径
			originalPath, err := rp.getOriginalPathFromFailedFile(path)
			if err != nil {
				fmt.Printf("⚠️ 无法读取失败文件 %s 的原始路径: %v\n", info.Name(), err)
				return nil
			}

			failedFiles = append(failedFiles, FailedFileInfo{
				FileName:     info.Name(),
				FailedPath:   path,
				OriginalPath: originalPath,
			})
		}
		return nil
	})

	return failedFiles, err
}

// getOriginalPathFromFailedFile 从失败文件中获取原始文件路径
func (rp *RetryProcessor) getOriginalPathFromFailedFile(failedFilePath string) (string, error) {
	content, err := os.ReadFile(failedFilePath)
	if err != nil {
		return "", err
	}

	lines := strings.Split(string(content), "\n")
	for i := len(lines) - 1; i >= 0; i-- {
		line := strings.TrimSpace(lines[i])
		if strings.HasPrefix(line, "原始文件路径: ") {
			return strings.TrimPrefix(line, "原始文件路径: "), nil
		}
	}

	return "", fmt.Errorf("未找到原始文件路径")
}

// testQwenConnection 测试新模型API连接
func (rp *RetryProcessor) testQwenConnection() error {
	fmt.Printf("🔧 测试 %s 模型连接...\n", rp.config.Model)
	
	request := QwenRequest{
		Model:       rp.config.Model,
		Temperature: rp.config.Temperature,
		MaxTokens:   50,
		Messages: []Message{
			{Role: "user", Content: "测试连接，请回复：连接成功"},
		},
	}

	jsonData, err := json.Marshal(request)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("POST", rp.config.BaseURL+"/chat/completions", bytes.NewBuffer(jsonData))
	if err != nil {
		return err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+rp.config.APIKey)

	resp, err := rp.client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return fmt.Errorf("连接失败: %d %s", resp.StatusCode, string(body))
	}

	fmt.Printf("✅ %s 模型连接测试成功\n", rp.config.Model)
	return nil
}

// cleanHTMLContent 清理HTML内容（复用main.go的方法）
func (rp *RetryProcessor) cleanHTMLContent(htmlContent string) (string, error) {
	doc, err := html.Parse(strings.NewReader(htmlContent))
	if err != nil {
		return "", err
	}

	var extractText func(*html.Node) string
	extractText = func(n *html.Node) string {
		if n.Type == html.TextNode {
			text := strings.TrimSpace(n.Data)
			if len(text) > 10 {
				return text + "\n"
			}
		}

		if n.Type == html.ElementNode {
			skipTags := map[string]bool{
				"script": true, "style": true, "nav": true,
				"header": true, "footer": true, "aside": true,
			}
			if skipTags[n.Data] {
				return ""
			}

			if strings.HasPrefix(n.Data, "h") && len(n.Data) == 2 {
				return fmt.Sprintf("\n\n[%s]%s[/%s]\n", strings.ToUpper(n.Data), getTextContent(n), strings.ToUpper(n.Data))
			}
		}

		var result string
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			result += extractText(c)
		}
		return result
	}

	content := extractText(doc)
	re := regexp.MustCompile(`\n{3,}`)
	content = re.ReplaceAllString(content, "\n\n")

	contentRunes := []rune(content)
	if len(contentRunes) > 16000 {
		content = string(contentRunes[:16000]) + "...[内容过长，已截断]"
	}

	return strings.TrimSpace(content), nil
}

// callQwenLLMWithResponse 调用Qwen LLM API并返回原始响应
func (rp *RetryProcessor) callQwenLLMWithResponse(content string) (*LLMResult, string, error) {
	request := QwenRequest{
		Model:       rp.config.Model,
		Temperature: rp.config.Temperature,
		MaxTokens:   16000,
		Messages: []Message{
			{Role: "system", Content: rp.systemPrompt},
			{Role: "user", Content: fmt.Sprintf("请分析以下HTML内容并返回JSON格式结果：\n\n%s", content)},
		},
	}

	jsonData, err := json.Marshal(request)
	if err != nil {
		return nil, "", err
	}

	req, err := http.NewRequest("POST", rp.config.BaseURL+"/chat/completions", bytes.NewBuffer(jsonData))
	if err != nil {
		return nil, "", err
	}

	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Authorization", "Bearer "+rp.config.APIKey)

	resp, err := rp.client.Do(req)
	if err != nil {
		return nil, "", err
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		body, _ := io.ReadAll(resp.Body)
		return nil, "", fmt.Errorf("API request failed: %d %s", resp.StatusCode, string(body))
	}

	var qwenResp QwenResponse
	if err := json.NewDecoder(resp.Body).Decode(&qwenResp); err != nil {
		return nil, "", err
	}

	if len(qwenResp.Choices) == 0 {
		return nil, "", fmt.Errorf("no response choices")
	}

	llmOutput := qwenResp.Choices[0].Message.Content
	result, parseErr := rp.parseJSONOutput(llmOutput)
	return result, llmOutput, parseErr
}

// parseJSONOutput 解析LLM的JSON输出
func (rp *RetryProcessor) parseJSONOutput(output string) (*LLMResult, error) {
	re := regexp.MustCompile("```json\\n?(.*?)\\n?```")
	matches := re.FindStringSubmatch(output)
	if len(matches) > 1 {
		output = matches[1]
	} else {
		start := strings.Index(output, "{")
		end := strings.LastIndex(output, "}")
		if start != -1 && end != -1 {
			output = output[start : end+1]
		}
	}

	re = regexp.MustCompile(`,(\s*[}\]])`)
	output = re.ReplaceAllString(output, "$1")

	var result LLMResult
	if err := json.Unmarshal([]byte(output), &result); err != nil {
		outputRunes := []rune(output)
		maxLen := min(200, len(outputRunes))
		return nil, fmt.Errorf("JSON parsing failed: %v, content: %s", err, string(outputRunes[:maxLen]))
	}

	return &result, nil
}

// processFailedFile 处理单个失败文件，允许重试一次
func (rp *RetryProcessor) processFailedFile(failedInfo FailedFileInfo) (bool, error) {
	// 将相对路径转换为绝对路径
	var originalFilePath string
	if strings.HasPrefix(failedInfo.OriginalPath, "../") {
		// 处理相对路径：../佛大官网的html静态页面/xxx -> 直接使用原始记录的路径
		originalFilePath = failedInfo.OriginalPath
	} else {
		originalFilePath = failedInfo.OriginalPath
	}

	// 检查原始文件是否存在
	if _, err := os.Stat(originalFilePath); os.IsNotExist(err) {
		return false, fmt.Errorf("原始文件不存在: %s", originalFilePath)
	}

	// 读取原始HTML文件
	content, err := os.ReadFile(originalFilePath)
	if err != nil {
		return false, fmt.Errorf("读取原始文件失败: %v", err)
	}

	// 文件太小跳过
	if len(content) < 500 {
		return false, fmt.Errorf("文件过小")
	}

	// 清理HTML内容
	cleanedContent, err := rp.cleanHTMLContent(string(content))
	if err != nil {
		return false, fmt.Errorf("HTML清理失败: %v", err)
	}

	if len(cleanedContent) < 100 {
		return false, fmt.Errorf("清理后内容过小")
	}

	fmt.Printf("🔄 正在重新处理: %s\n", filepath.Base(originalFilePath))

	// 尝试处理，最多重试一次
	var lastErr error
	
	for attempt := 1; attempt <= 2; attempt++ {
		result, _, err := rp.callQwenLLMWithResponse(cleanedContent)
		
		if err == nil {
			// 成功处理，生成文档并保存
			doc, buildErr := rp.buildProcessedDocument(result, originalFilePath)
			if buildErr != nil {
				return false, fmt.Errorf("构建文档失败: %v", buildErr)
			}
			
			// 保存到主数据文件中
			if saveErr := rp.appendToMainFiles(*doc); saveErr != nil {
				return false, fmt.Errorf("保存到主文件失败: %v", saveErr)
			}
			
			fmt.Printf("✅ 重新处理成功: %s\n", filepath.Base(originalFilePath))
			return true, nil
		}
		
		lastErr = err
		
		if attempt == 1 {
			// 第一次失败，等待后重试
			waitTime := 3 * time.Second
			fmt.Printf("⏳ 第一次尝试失败，等待 %v 后重试: %v\n", waitTime, err)
			time.Sleep(waitTime)
		}
	}
	
	// 两次尝试都失败，记录最后的错误
	fmt.Printf("❌ 重新处理失败: %s, 错误: %v\n", filepath.Base(originalFilePath), lastErr)
	return false, fmt.Errorf("重试后仍失败: %v", lastErr)
}

// buildProcessedDocument 构建处理后的文档
func (rp *RetryProcessor) buildProcessedDocument(llmResult *LLMResult, filePath string) (*ProcessedDocument, error) {
	relPath, err := filepath.Rel(rp.htmlBasePath, filePath)
	if err != nil {
		return nil, err
	}

	category, subcategory := rp.determineCategory(relPath)
	hash := md5.Sum([]byte(relPath))
	docID := fmt.Sprintf("%s_%x", category, hash[:6])
	url := fmt.Sprintf("https://www.fosu.edu.cn/%s", strings.TrimSuffix(relPath, ".html"))

	doc := &ProcessedDocument{
		ID:          docID,
		Title:       llmResult.Title,
		Category:    category,
		Subcategory: subcategory,
		PublishDate: llmResult.PublishDate,
		SourcePath:  relPath,
		URL:         url,
		Keywords:    llmResult.Keywords,
		Summary:     llmResult.Summary,
		Content:     llmResult.Content,
		QAPairs:     llmResult.QAPairs,
		Language:    "zh-CN",
		Metadata: map[string]interface{}{
			"extraction_timestamp": time.Now().Format(time.RFC3339),
			"processing_version":   "go_retry_v1.0",
			"llm_model":            rp.config.Model,
			"api_provider":         "Qwen",
			"total_qa_pairs":       len(llmResult.QAPairs),
			"retry_processed":      true,
		},
	}

	return doc, nil
}

// determineCategory 根据文件路径确定分类
func (rp *RetryProcessor) determineCategory(filePath string) (string, string) {
	parts := strings.Split(filePath, "/")

	for _, part := range parts {
		if subcategory, exists := rp.categoryMapping[part]; exists {
			return part, subcategory
		}
	}

	return "other", "其他"
}

// appendToMainFiles 将成功处理的文档追加到主文件
func (rp *RetryProcessor) appendToMainFiles(doc ProcessedDocument) error {
	// 读取现有的主文件
	mainFilePath := filepath.Join(rp.outputPath, "foshan_university_go.json")
	var existingDocs []ProcessedDocument

	if _, err := os.Stat(mainFilePath); err == nil {
		// 文件存在，读取现有数据
		content, readErr := os.ReadFile(mainFilePath)
		if readErr != nil {
			return readErr
		}
		if jsonErr := json.Unmarshal(content, &existingDocs); jsonErr != nil {
			return jsonErr
		}
	}

	// 检查是否已存在相同ID的文档，如果存在则替换，否则追加
	found := false
	for i, existingDoc := range existingDocs {
		if existingDoc.ID == doc.ID {
			existingDocs[i] = doc
			found = true
			break
		}
	}

	if !found {
		existingDocs = append(existingDocs, doc)
	}

	// 保存更新后的文档
	if err := rp.saveJSON(existingDocs, "foshan_university_go.json"); err != nil {
		return err
	}

	// 生成并更新训练数据
	trainingData := rp.generateTrainingData(existingDocs)
	if err := rp.saveJSON(trainingData, "foshan_university_training_go.json"); err != nil {
		return err
	}

	// 生成并更新RAG数据
	ragChunks := rp.generateRAGChunks(existingDocs)
	if err := rp.saveJSON(ragChunks, "foshan_university_rag_go.json"); err != nil {
		return err
	}

	return nil
}

// generateTrainingData 生成LoRA训练数据
func (rp *RetryProcessor) generateTrainingData(docs []ProcessedDocument) []TrainingData {
	var trainingData []TrainingData

	for _, doc := range docs {
		for _, qa := range doc.QAPairs {
			instruction := fmt.Sprintf("你是佛山大学的智能问答助手。以下问题来自《%s》（%s），请根据文档内容准确回答。",
				doc.Title, doc.Subcategory)

			trainingData = append(trainingData, TrainingData{
				Instruction: instruction,
				Input:       qa.Question,
				Output:      qa.Answer,
				Type:        qa.Type,
				SourceID:    doc.ID,
				Title:       doc.Title,
				Category:    doc.Subcategory,
			})
		}
	}

	return trainingData
}

// generateRAGChunks 生成RAG数据块
func (rp *RetryProcessor) generateRAGChunks(docs []ProcessedDocument) []RAGChunk {
	var chunks []RAGChunk
	chunkSize := 512

	for _, doc := range docs {
		content := doc.Content
		contentRunes := []rune(content)

		if len(contentRunes) > chunkSize {
			chunkIndex := 0
			for i := 0; i < len(contentRunes); i += chunkSize {
				end := i + chunkSize
				if end > len(contentRunes) {
					end = len(contentRunes)
				}

				chunk := RAGChunk{
					ChunkID:   fmt.Sprintf("%s_chunk_%d", doc.ID, chunkIndex),
					TextChunk: string(contentRunes[i:end]),
					Metadata: map[string]interface{}{
						"source_doc":  doc.ID,
						"title":       doc.Title,
						"category":    doc.Category,
						"chunk_type":  "content",
						"chunk_index": chunkIndex,
						"source_url":  doc.URL,
					},
				}
				chunks = append(chunks, chunk)
				chunkIndex++
			}
		} else {
			chunk := RAGChunk{
				ChunkID:   fmt.Sprintf("%s_chunk_0", doc.ID),
				TextChunk: string(contentRunes),
				Metadata: map[string]interface{}{
					"source_doc":  doc.ID,
					"title":       doc.Title,
					"category":    doc.Category,
					"chunk_type":  "content",
					"chunk_index": 0,
					"source_url":  doc.URL,
				},
			}
			chunks = append(chunks, chunk)
		}

		// 为每个问答对创建独立的块
		for i, qa := range doc.QAPairs {
			qaPair := fmt.Sprintf("问题: %s\n答案: %s", qa.Question, qa.Answer)
			chunk := RAGChunk{
				ChunkID:   fmt.Sprintf("%s_qa_%d", doc.ID, i),
				TextChunk: qaPair,
				Metadata: map[string]interface{}{
					"source_doc": doc.ID,
					"title":      doc.Title,
					"category":   doc.Category,
					"chunk_type": "qa_pair",
					"qa_type":    qa.Type,
					"difficulty": qa.Difficulty,
					"source_url": doc.URL,
				},
			}
			chunks = append(chunks, chunk)
		}
	}

	return chunks
}

// saveJSON 保存JSON文件
func (rp *RetryProcessor) saveJSON(data interface{}, filename string) error {
	file, err := os.Create(filepath.Join(rp.outputPath, filename))
	if err != nil {
		return err
	}
	defer file.Close()

	encoder := json.NewEncoder(file)
	encoder.SetIndent("", "  ")
	encoder.SetEscapeHTML(false)
	return encoder.Encode(data)
}

// ProcessFailedFiles 处理所有失败文件
func (rp *RetryProcessor) ProcessFailedFiles() error {
	// 测试API连接
	if err := rp.testQwenConnection(); err != nil {
		return fmt.Errorf("API连接测试失败: %v", err)
	}

	// 获取所有失败文件
	failedFiles, err := rp.getFailedFiles()
	if err != nil {
		return fmt.Errorf("获取失败文件列表失败: %v", err)
	}

	if len(failedFiles) == 0 {
		fmt.Println("🎉 没有发现失败文件，无需重试处理")
		return nil
	}

	fmt.Printf("📋 发现 %d 个失败文件需要重新处理\n", len(failedFiles))

	// 并发处理失败文件
	var mu sync.Mutex
	var wg sync.WaitGroup
	semaphore := make(chan struct{}, rp.concurrency)

	successCount := 0
	failedCount := 0
	processedCount := 0

	for _, failedFile := range failedFiles {
		wg.Add(1)
		go func(failed FailedFileInfo) {
			defer wg.Done()
			semaphore <- struct{}{}
			defer func() { <-semaphore }()

			success, err := rp.processFailedFile(failed)

			mu.Lock()
			processedCount++
			if success {
				successCount++
				// 删除失败文件记录
				if removeErr := os.Remove(failed.FailedPath); removeErr != nil {
					fmt.Printf("⚠️ 删除失败文件记录失败: %s, 错误: %v\n", failed.FileName, removeErr)
				} else {
					fmt.Printf("🗑️ 已删除失败文件记录: %s\n", failed.FileName)
				}
			} else {
				failedCount++
				fmt.Printf("❌ 处理失败: %s, 错误: %v\n", failed.FileName, err)
			}

			// 每10个文件报告进度
			if processedCount%10 == 0 {
				fmt.Printf("📊 进度报告: 已处理 %d/%d, 成功: %d, 失败: %d\n", 
					processedCount, len(failedFiles), successCount, failedCount)
			}
			mu.Unlock()

			// 添加延迟避免API过载
			time.Sleep(2 * time.Second)
		}(failedFile)
	}

	wg.Wait()

	fmt.Printf("\n🎯 重试处理完成！\n")
	fmt.Printf("- 总文件数: %d\n", len(failedFiles))
	fmt.Printf("- 成功处理: %d\n", successCount)
	fmt.Printf("- 仍然失败: %d\n", failedCount)
	fmt.Printf("- 成功率: %.2f%%\n", float64(successCount)/float64(len(failedFiles))*100)

	return nil
}

